﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Roslyn.Compilers.CSharp;
using Roslyn.Compilers;
using System.Threading;
using Logging;
using System.IO;
using CompleterLib;

namespace SelectionAddin.Extension.Test
{
    public interface ICompleterTest
    {
        string Name { get; }
        bool RunTest();

        IEnumerable<string> GetResultStrings();
        string GetRewriteString();
        string GetTypeString();
    }
    abstract class CompleterTest : ICompleterTest
    {
        protected abstract string Code { get; }
        public abstract string Name { get; }
        const string DOT_NET_IMPLEMENTATION_DIR = @"C:\Windows\Microsoft.NET\Framework\v4.0.30319\";
        protected virtual MetadataReference[] References
        {
            get
            {
                return new string[] {
                    "mscorlib",
                    "Microsoft.CSharp",
                    "System",
                    "System.Core",
                    "System.Data",
                    "System.Data.DataSetExtensions",
                    "System.Xml",
                    "System.Xml.Linq",
                }.Select(n => new AssemblyFileReference(DOT_NET_IMPLEMENTATION_DIR + n + ".dll"))
                 .ToArray();
            }
        }

        protected Compilation CompileTest()
        {
            SyntaxTree tree = SyntaxTree.ParseCompilationUnit(Code);
            return Compilation.Create(outputName: Name,
                                      syntaxTrees: new[] { tree },
                                      references: References);
        }
        protected virtual ExpressionSyntax ToComplete(Compilation c)
        {
            SyntaxTree tree = c.SyntaxTrees[0];
            int startIdx = Code.IndexOf("/*^*/");
            int endIdx = Code.IndexOf("/*$*/");
            // TODO Is this right?
            TextSpan span = new TextSpan(startIdx + 5, endIdx - startIdx + 1 - 5 - 1);
            string test = tree.GetText(default(CancellationToken)).GetText(span);
            SyntaxNode node = tree.GetRoot(default(CancellationToken));
            while (!node.Span.Equals(span))
            {
                try
                {
                    node = node.ChildNodes().First(child => child.Span.Contains(span));
                }
                catch (InvalidOperationException)
                {
                    break;
                }
            }
            if (node is ArgumentSyntax)
            {
                return (node as ArgumentSyntax).Expression;
            }
            else
            {
                return (ExpressionSyntax)node;
            }
        }
        protected IEnumerable<Completion> GetResults(Compilation c,
                                                     ExpressionSyntax exp)
        {
            return new CompleterLib.Completer(c.GetSemanticModel(c.SyntaxTrees[0]), exp.Span.Start)
                .GetCompletions(exp);
        }
        protected IEnumerable<Completion> GetResults(Compilation c)
        {
            return GetResults(c, ToComplete(c));
        }

        public abstract bool RunTest();

        string ICompleterTest.Name
        {
            get { throw new NotImplementedException(); }
        }

        IEnumerable<string> ICompleterTest.GetResultStrings()
        {
            return GetResults(CompileTest()).Select(c => c.ToString());
        }



        bool ICompleterTest.RunTest()
        {
            throw new NotImplementedException();
        }

        string ICompleterTest.GetRewriteString()
        {
            string res;
            var c = CompileTest();
            ToComplete(c).RewritePartial(c.GetSemanticModel(c.SyntaxTrees[0]), out res);
            return res;
        }
        string ICompleterTest.GetTypeString()
        {
            var c = CompileTest();
            var t = ToComplete(c).GetRetType(c.GetSemanticModel(c.SyntaxTrees[0]));
            if (t == null)
            {
                return "(null)";
            }
            if (t.TypeKind.Equals(TypeKind.Error))
            {
                return "(Error) " + t.ToDisplayString();
            }
            else
            {
                return t.ToString();
            }
        }
    }
    class CompleterRankBoundTest : CompleterTest
    {
        private readonly string code;
        private readonly string name;
        private readonly string correct;
        private readonly int maxRank;

        public CompleterRankBoundTest(string name, string code, string correct, int maxRank)
        {
            this.name = name;
            this.code = code;
            this.correct = correct;
            this.maxRank = maxRank;
        }

        public override bool RunTest()
        {
            IEnumerable<Completion> results = GetResults(CompileTest());
            return results.Take(maxRank)
                          .Any(completion => completion.CompletionText.Equals(correct));
        }

        protected override string Code
        {
            get { return code; }
        }

        public override string Name
        {
            get { return name; }
        }
    }
    public class CompleterRankBoundTestInstance
    {
        private readonly string code;
        private readonly string name;
        private readonly string correct;
        private readonly int maxRank;

        public CompleterRankBoundTestInstance(string name, string code, string correct, int maxRank)
        {
            this.name = name;
            this.code = code;
            this.correct = correct;
            this.maxRank = maxRank;
        }

        public ICompleterTest Test
        {
            get
            {
                return new CompleterRankBoundTest(name, code, correct, maxRank);
            }
        }
    }

    public static class CompleterTestUtil
    {
        public static void RunTests(this IEnumerable<ICompleterTest> tests)
        {
            int testCount = 0;
            int passedCount = 0;
            foreach (var test in tests)
            {
                testCount++;
                bool passed = false;
                try { passed = test.RunTest(); }
                catch (Exception e)
                {
                    Logger.PrintLine("Test " + test.Name
                        + " threw Exception: " + e.ToString());
                }

                if (!passed)
                {
                    Logger.PrintLine("Test failed: " + test.Name);
                }
                else
                {
                    passedCount++;
                }
            }
            Logger.PrintLine("Finished testing: "
                + passedCount + " / " + testCount + " tests passed.");
        }
        public static void RunTests(this IEnumerable<CompleterRankBoundTestInstance> tests)
        {
            tests.Select(t => t.Test).RunTests();
        }
    }

    static class CompleterTester
    {
        public static void Main(string[] args)
        {
            string logFile = "s:\\log\\" + "CompleterTester"
                + "." + DateTime.Now.ToString("s").Replace(":", "")
                + ".txt";
            Logger.SetOutputFile(File.Open(logFile, FileMode.Create));

            new CompleterTest[] {
                new CompleterRankBoundTest("HelloWorld",
                    @"using System;
    
                    namespace HelloWorld
                    {
                        class Program
                        {
                            static void Main(string[] args)
                            {
                                /*^*/m(""Hello, World!"")/*$*/
                            }
                        }
                    }",
                    @"Console.WriteLine(""Hello, World!"")", 10)
            }.RunTests();
        }
    }
}
